"""选举算法--环算法"""
import random

"""
基本思想：
该环算法不适用令牌，假设进程按照物理或者逻辑顺序进行排序，那么进程都知道它的后继者。
当任何一个进程注意到协调者不工作时，它构造一个带有自己的进程号的election消息，并将消息发送给后继者。
如果后继者崩溃了，发送者沿着此环跳过它的后继者发送给下一个进程，或者再下一个进程。直到找到一个正在运行的进程。
在每一步中，发送者将自己的进程编号加入到消息中，以使自己成为协调者候选人之一。
最终消息返回到发起这次选举的进程，当发起者收到一条包含自己进程编号的消息时，识别出来。此时消息编程coordinator消息，
并再一次绕环运行，向所有进程通知谁是协调者（成员列表中进程号最大的那个）
"""

import time
from datetime import datetime
from threading import Lock, Thread


class Participant:
    def __init__(self, participant_id, coordinator):
        self.coordinator = coordinator  # 协调者对象
        self.participant_id = participant_id  # 参与者id,主线程创建该参与者时提供
        self.participant_list = self.coordinator.participant_list  # 参与者列表
        self.ring_list = []
        self.thread_list = self.coordinator.thread_list  # 线程列表，选举时用
        self.thread_id = self.participant_id  # 自己的线程id
        self.coordinator_thread_index = self.coordinator.index  # 协调者线程在线程列表的索引
        self.request_list = self.coordinator.request_list  # 绑定协调者的请求列表
        self.request_time = datetime.now()  # 请求时间
        self.get_response = True  # 是否得到响应
        self.is_using = False  # 是否正在使用
        self.lock = self.coordinator.lock  # 绑定同一个资源互斥锁
        self.is_participant = True  # 是否是参与者的标志

    def start(self):
        ring_list = self.thread_list[self.thread_id:-1]
        for item in self.thread_list[0:self.thread_id]:
            ring_list.append(item)
        self.ring_list = ring_list
        while 1:
            self.coordinator_thread_index = self.coordinator.index  # 更新当前协调者线程在线程列表的索引
            if self.get_response:  # 如果请求已被处理，则可以发起下一次请求
                self.get_response = False
                self.request_time = datetime.now()
                self.send_request()
                self.working()  # 进入工作阻塞状态，直到收到响应
            if not self.is_participant:  # 如果自己已经不是参与者，应当退出
                return
            time.sleep(2)

    def send_request(self):
        self.request_list.append(f'{self.request_time}_{self.participant_id}')
        self.request_list.sort()
        print(f'参与者{self.participant_id}:已于{self.request_time} 发送请求，正在等待响应！')

    def working(self):
        while 1:
            response_obj = self.request_list[0]  # 协调者当前应当处理的请求
            if response_obj == f'{self.request_time}_{self.participant_id}':  # 如果当前该处理的是自己的请求
                wait_start = datetime.now()  # 开始等待的时间
                while not self.get_response:
                    wait_time = (datetime.now() - wait_start).total_seconds()  # 计算等待时间
                    if wait_time > 10:  # 超过10秒未收到响应，认为当前协调者出现问题
                        print(f'参与者{self.participant_id}:检测到协调者响应超时，发起选举！')
                        with self.lock:
                            self.election_1()  # 发起情况1的选举
                        break  # 跳出等待循环

            if self.get_response:  # 得到允许表示有资格
                with self.lock:  # 但是还得拿到互斥锁才能访问
                    self.is_using = True
                    print(f'参与者{self.participant_id}:请求已被协调者允许，开始访问临界资源！')
                    # TODO：此处可以为访问临界资源的相关操作,此处用睡眠1秒代替
                    time.sleep(1)
                    print(f'参与者{self.participant_id}:访问临界资源完成，可以继续工作了！')
                    self.is_using = False
                    return

    # 进行第一种情况的选举，由于得不到响应而发起选举
    def election_1(self):
        self.coordinator_thread_index = self.thread_id  # 如果遍历未找到可成为协调者的线程，则默认自己成为协调者
        candidate_list = self.ring_list  # 根据环列表逐个判断谁能成为协调者
        for thread in candidate_list[1:-1]:
            if thread.is_alive():
                self.coordinator_thread_index = int(thread.getName().split('_')[1])
                break
        else:
            return
        self.participant_list[self.coordinator_thread_index].is_participant = False
        self.coordinator.index = self.coordinator_thread_index  # 更新协调者索引值
        self.thread_list[self.coordinator_thread_index] = Thread(target=self.coordinator.start,
                                                                 name=f'coordinator_{self.coordinator_thread_index}')  # 设置线程执行协调者工作
        self.thread_list[self.coordinator_thread_index].setDaemon(True)  # 守护线程
        print(f'参与者{self.participant_id}:线程{self.coordinator_thread_index}已成为新的协调者！')
        self.thread_list[self.coordinator_thread_index].start()
        # self.thread_list[self.coordinator_thread_index].join()


class Coordinator:
    def __init__(self):
        self.thread_list = []  # 工作线程列表，初始最后一个为协调者
        self.index = len(self.thread_list)  # 协调者在工作线程列表的索引
        self.thread_id = self.index  # 自己的线程id
        self.participant_list = []
        self.request_list = []
        self.response_obj = ""
        self.response_time = datetime.now()
        self.lock = Lock()  # 临界资源访问的锁，针对参与者
        self.coordinator_lock = Lock()  # 针对协调者的锁
        self.is_participant = False  # 是否是参与者的标志

    # TODO：协调者只负责处理最早的请求，然后回复，可扩展为处理的同时修改请求对象的是否收到响应的属性
    #   这样就不需要在主线程再设置一个单独的检查者线程了
    def start(self):
        while 1:
            # print(self.request_list)
            if self.index != self.thread_id:  # id与索引不一致，说明发生过选举
                print(f'线程{self.thread_id}:线程{self.thread_id}恢复！')
                self.become_participant()  # 接替被变为协调者的参与者的工作
                return  # 自己已经失去协调者的资格，直接退出
            with self.coordinator_lock:
                self.send_response()
            time.sleep(1)

    def send_response(self):
        if len(self.request_list) > 0:
            self.response_obj = self.request_list[0]
            # TODO: 处理当前列表中最早的请求
            self.working()
            # 删除该请求，此处使用del的目的是在删除元素的同时释放其空间，节约内存，
            # 如果仅想达到删除列表内元素的目的，也可以使用列表自带的remove方法
            del self.request_list[0]

    def working(self):
        # TODO:已扩展为处理的同时修改请求对象的是否收到响应的属性
        #   这样就不需要在主线程再设置一个单独的检查者线程了，而且可以直接处理，不需要一直循环检查
        time.sleep(random.randint(6, 12))  # 使用随机阻塞时长模拟超时，大于10则会被判超时
        response = self.response_obj.split('_')[1]  # 当前处理的参与者id
        response = int(response)
        self.participant_list[response].get_response = True  # 设置该参与者是否得到响应的标志为True
        self.response_time = datetime.now()  # 响应时间
        print(f'协调者：来自参与者{response}的请求处理完成！处理时间：{self.response_time}')
        while self.participant_list[response].is_using:
            time.sleep(0.2)  # 每0.2秒检查该参与者是否还在使用该临界资源
            continue

    def become_participant(self):
        # 接替被变为协调者的参与者的工作
        participant = self.participant_list[self.index]
        participant.is_participant = True
        participant.thread_id = self.thread_id  # 互换id
        self.thread_id = self.index
        self.thread_list[self.thread_id] = Thread(target=participant.start,
                                                  name=f'participant_{participant.participant_id}')
        self.thread_list[self.thread_id].setDaemon(True)  # 守护线程
        print(f'线程{participant.thread_id}:线程{participant.thread_id}已接替参与者{participant.participant_id}的工作')
        self.thread_list[self.thread_id].start()
        # self.thread_list[self.index].join()
